#include <gtest/gtest.h>

#include <iostream>
#include <vector>
#include <boost/smart_ptr.hpp>

#include "Globals.h"
//#include "Display.h"
//#include "BitmapDraw.h"
//#include "Input.h"
#include "GameElement.h"
#include "LifeForm.h"
#include "Man.h"
#include "Monster.h"
#include "Soil.h"
#include "Arrow.h"
//#include "Render.h"

using namespace std;
using boost::scoped_ptr;
using boost::shared_ptr;

//--------------------------------------------
//INDEX
//--------------------------------------------

//1. MAN OBJECT TESTS
//2. MONSTER OBJECT TESTS
//3. ARROW OBJECT TESTS
//4. SOIL OBJECT TESTS
//5. MAN WITH MONSTER COLLISION TESTS
//6. ARROW WITH MONSTER COLLISION TESTS

//--------------------------------------------

//--------------------------------------------
//1. MAN OBJECT TESTS
//--------------------------------------------

//TEST 1.1: MAN IS INITIALISED TO THE CENTRE OF THE SCREEN

TEST(Man, testManInitToCentreOfScreen)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();
    int midScreen_x = 650;  //horizontal centre of the screen
    int midScreen_y = 350;  //vertical centre of the screen

    EXPECT_TRUE ( midScreen_x == testMan->get_pos_x() &&  midScreen_y == testMan->get_pos_y() );
}

//TEST 1.2: MAN MOVES UP WHEN "move_up" function is called
TEST(Man, testManMovesUp)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    int expected_pos_x = 650;   //expected positopns after move_up function called
    int expected_pos_y = 340;   //moves up by 10 because _speed_y = 10

    testMan -> move_up();
    testMan -> update();

    EXPECT_TRUE (expected_pos_x == testMan->get_pos_x() && expected_pos_y == testMan->get_pos_y());
}

//TEST 1.3: MAN MOVES DOWN WHEN "move_down" function is called
TEST(Man, testManMovesDown)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    int expected_pos_x = 650;   //expected positopns after move_down function called
    int expected_pos_y = 360;   //moves down by 10 because _speed_y = 10

    testMan -> move_down();
    testMan -> update();

    EXPECT_TRUE (expected_pos_x == testMan->get_pos_x() && expected_pos_y == testMan->get_pos_y());
}

//TEST 1.4: MAN MOVES LEFT WHEN "move_left" function is called
TEST(Man, testManMovesLeft)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    int expected_pos_x = 640;   //expected positopns after move_left function called
    int expected_pos_y = 350;   //moves left by 10 because _speed_x = 10

    testMan -> move_left();
    testMan -> update();

    EXPECT_TRUE ( expected_pos_x == testMan->get_pos_x() && expected_pos_y == testMan->get_pos_y());
}

//TEST 1.5: MAN MOVES RIGHT WHEN "move_right" function is called
TEST(Man, testManMovesRight)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    int expected_pos_x = 660;   //expected positions after move_right function called
    int expected_pos_y = 350;   //moves right by 10 because _speed_x = 10

    testMan -> move_right();
    testMan -> update();

    EXPECT_TRUE ( expected_pos_x == testMan->get_pos_x() && expected_pos_y == testMan->get_pos_y());
}

//Note: THE SOIL IS FROM (0,100) TO (1300,700) ON THE DISPLAY

//TEST 1.6: MAN DOES NOT GO OVER THE TOP EDGE OF THE SOIL
//          BUT REMAINS AT THE EDGE
TEST(Man, testManDoesNotGoOverTopEdgeOfGameScreen)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    testMan -> set_pos_y(100);  //moves testMan to position (650,100)

    testMan -> move_up();
    testMan -> update();

    EXPECT_TRUE ( (testMan->get_pos_y() == 100) && !(testMan->get_pos_y() < 0) ) ;
}
//TEST 1.7: MAN DOES NOT GO OVER THE BOTTOM EDGE OF THE SOIL
//          BUT REMAINS AT THE EDGE
TEST(Man, testManDoesNotGoOverBottomEdgeOfGameScreen)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    testMan -> set_pos_y(650);  //moves testMan to position (650,650)

    testMan -> move_down();
    testMan -> update();

    EXPECT_TRUE ( (testMan->get_pos_y() == 650) && !(testMan->get_pos_y() > 650) );
}

//TEST 1.8: MAN DOES NOT GO OVER THE RIGHT EDGE OF THE SOIL
//          BUT REMAINS AT THE EDGE
TEST(Man, testManDoesNotGoOverRightEdgeOfGameScreen)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    testMan -> set_pos_x(1250); //moves testMan to position (1250,350)

    testMan -> move_right();
    testMan -> update();

    EXPECT_TRUE ( (testMan->get_pos_x() == 1250) && !(testMan->get_pos_x() > 1250) );
}

//TEST 1.9: MAN DOES NOT GO OVER THE LEFT EDGE OF THE SOIL
//          BUT REMAINS AT THE EDGE
TEST(Man, testManDoesNotGoOverLeftEdgeOfGameScreen)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    testMan -> set_pos_x(0);    //moves testMan to position (0,350)

    testMan -> move_left();
    testMan -> update();

    EXPECT_TRUE ( (testMan->get_pos_x() == 0) && !(testMan->get_pos_x() < 0) );
}

//TEST 1.10: SCORE 100 POINTS WHEN "score_point" is called
TEST(Man, scoreOneHundredPoints)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    testMan -> score_point();
    testMan -> update();

    EXPECT_TRUE ( testMan -> get_score() == 100 );
}

//------------------------------------------
//2. MONSTER OBJECT TESTS
//------------------------------------------

//Test 2.1 : MONSTER IS INITIALISED RADNOMLY TO ONE OF THE FOUR INITIAL TUNNELS
//           AND TO A RANDOM POSITION WITHIN THESE TUNNELS
TEST(Monster, monsterInitializedToOneOfFourTunels)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    shared_ptr<Monster > testMonster ( new Monster(testMan, soil) );
    testMonster -> initialise();

    int x_position = testMonster -> get_pos_x();
    int y_position = testMonster -> get_pos_y();

    EXPECT_TRUE ( ( y_position > 150 && y_position < 300 )  ||
                  ( x_position > 150 && x_position < 450)   ||
                  ( x_position > 950 && x_position < 1200 ) ||
                  ( y_position > 400 && y_position < 650)   );
}

//TEST 2.2 : MONSTER MOVES AUTONOMOUSLY
TEST(Monster, monsterMovesAutonomously)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    shared_ptr<Monster > testMonster ( new Monster(testMan, soil) );
    testMonster -> initialise();

    int x_position = testMonster -> get_pos_x();
    int y_position = testMonster -> get_pos_y();

    testMonster -> update();

    EXPECT_FALSE ( x_position == testMonster->get_pos_x() && y_position == testMonster->get_pos_y() );
}

//------------------------------------------
//3. ARROW OBJECT TESTS
//------------------------------------------

//TEST 3.1: "_arrowExpand" IS INCREMENTED WHEN "_expand" IS SET AND ARROW IS UPDATED
TEST(Arrow, arrowExpandIncrementedAtUpdate )
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Arrow > arrow ( new Arrow(testMan) );
    arrow -> initialise();

    arrow -> set_expand();
    arrow -> update();

    int expected_1 = 1;
    int actual_1 = arrow->get_arrowExpand();

    arrow -> update();

    int expected_2 = 2;
    int actual_2 = arrow->get_arrowExpand();

    arrow -> update();
    arrow -> update();
    arrow -> update();

    int expected_3 = 5;
    int actual_3 = arrow->get_arrowExpand();

    EXPECT_TRUE( expected_1 == actual_1 &&
                 expected_2 == actual_2 &&
                 expected_3 == actual_3 );
}

//TEST 3.2: THE ARROW BEGINS TO RETRACT WHEN "_arrowExpand" REACHES A MAX OF 15
TEST(Arrow, arrowRetractsWhenArrowExpandReachesMax )
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Arrow > arrow ( new Arrow(testMan) );
    arrow -> initialise();

    arrow -> set_expand();

    for (int i = 0; i<15; i++)
    {
        arrow -> update();
    }

    int expected_1 = 15;
    int actual_1 = arrow->get_arrowExpand();

    arrow -> update();
    arrow -> update();

    int expected_2 = 14;
    int actual_2 = arrow->get_arrowExpand();

    arrow -> update();
    arrow -> update();
    arrow -> update();

    int expected_3 = 11;
    int actual_3 = arrow->get_arrowExpand();

    EXPECT_TRUE( expected_1 == actual_1 &&
                 expected_2 == actual_2 &&
                 expected_3 == actual_3 );
}

//TEST 3.2: THE POSITION OF THE ARROW GOES TO (0,0) WHEN NOT BEING FIRED SO NOT TO BE ABLE TO COLLIDE
TEST(Arrow, arrowGoesToZeroZeroWhenRetracted )
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Arrow > arrow ( new Arrow(testMan) );
    arrow -> initialise();

    arrow -> set_expand();

    for (int i = 0; i<15; i++)  //expand arrow
    {
        arrow -> update();
    }

    for (int i = 0; i<16; i++)  //contract arrow
    {
        arrow -> update();
    }

    int expected_pos_x = 0;
    int actual_pos_x = arrow->get_pos_x();

    int expected_pos_y = 0;
    int actual_pos_y = arrow->get_pos_y();

    EXPECT_TRUE( expected_pos_x == actual_pos_x &&
                 expected_pos_y == actual_pos_y );
}

//------------------------------------------
//4. SOIL OBJECT TESTS
//------------------------------------------

//TEST 4.1 SOIL HOLE TRACKING VECTOR KEEPS TRACK OF WHERE MAN HAS BEEN AND DUG A TUNNEL
TEST(Soil, holeTrackingVectorKeepsTrackOfTunnels)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    testMan -> set_pos_x(300);
    testMan -> set_pos_y(300);

    testMan -> update();

    soil -> update();

    EXPECT_TRUE ( soil -> get_hole_tracker(7,5) );
}

//------------------------------------------
//5. MAN WITH MONSTER COLLISION TESTS
//------------------------------------------

//TEST 5.1 WHEN MONSTER APPROACHES FROM THE LEFT AND COLLIDES WITH MAN, "check_collision" RETURNS TRUE
TEST(Man, checkCollisionReturnTrueWhenManCollideWithMonsterApproachingFromLeft)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    shared_ptr<Monster > testMonster ( new Monster(testMan, soil) );
    testMonster -> initialise();

    testMan -> set_pos_x(1050);
    testMan -> set_pos_y(200);

    testMonster -> set_pos_x(1000);
    testMonster -> set_pos_y(200);

    testMan -> move_left();
    testMan -> update();

    EXPECT_TRUE ( testMan -> check_collision(testMonster->get_pos_x(), testMonster->get_pos_y()) );
}

//TEST 5.2 WHEN MONSTER APPROACHES FROM THE RIGHT AND COLLIDES WITH MAN, "check_collision" RETURNS TRUE
TEST(Man, checkCollisionReturnTrueWhenManCollideWithMonsterApproachingFromRight)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    shared_ptr<Monster > testMonster ( new Monster(testMan, soil) );
    testMonster -> initialise();

    testMan -> set_pos_x(1000);
    testMan -> set_pos_y(200);

    testMonster -> set_pos_x(1050);
    testMonster -> set_pos_y(200);

    testMan -> move_right();
    testMan -> update();

    EXPECT_TRUE ( testMan -> check_collision(testMonster->get_pos_x(), testMonster->get_pos_y()) );
}

//TEST 5.3 WHEN MONSTER APPROACHES FROM ABOVE AND COLLIDES WITH MAN, "check_collision" RETURNS TRUE
TEST(Man, checkCollisionReturnTrueWhenManCollideWithMonsterApproachingFromAbove)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    shared_ptr<Monster > testMonster ( new Monster(testMan, soil) );
    testMonster -> initialise();

    testMan -> set_pos_x(850);
    testMan -> set_pos_y(500);

    testMonster -> set_pos_x(850);
    testMonster -> set_pos_y(450);

    testMan -> move_up();
    testMan -> update();

    EXPECT_TRUE ( testMan -> check_collision(testMonster->get_pos_x(), testMonster->get_pos_y()) );
}

//TEST 5.4 WHEN MONSTER APPROACHES FROM BELOW AND COLLIDES WITH MAN, "check_collision" RETURNS TRUE
TEST(Man, checkCollisionReturnTrueWhenManCollideWithMonsterApproachingFromBelow)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    shared_ptr<Monster > testMonster ( new Monster(testMan, soil) );
    testMonster -> initialise();

    testMan -> set_pos_x(850);
    testMan -> set_pos_y(450);

    testMonster -> set_pos_x(850);
    testMonster -> set_pos_y(500);

    testMan -> move_down();
    testMan -> update();

    EXPECT_TRUE ( testMan -> check_collision(testMonster->get_pos_x(), testMonster->get_pos_y()) );
}

//------------------------------------------
//6. ARROW WITH MONSTER COLLISION TESTS
//------------------------------------------

//TEST 6.1 WHEN THE ARROW COLLIDES WITH A MONSTER "check_collision" returns true
TEST(Man, checkCollisionReturnTrueWhenArrowCollideWithMonster)
{
    shared_ptr<Man > testMan ( new Man() );
    testMan -> initialise();

    shared_ptr<Soil > soil ( new Soil(testMan) );
    soil -> initialise();

    shared_ptr<Monster > testMonster ( new Monster(testMan, soil) );
    testMonster -> initialise();

    shared_ptr<Arrow > arrow ( new Arrow(testMan) );
    arrow -> initialise();

    testMan -> set_pos_x(950);
    testMan -> set_pos_y(200);

    testMan -> move_right();
    testMan -> update();

    testMonster -> set_pos_x(1050);
    testMonster -> set_pos_y(200);

    for ( int i = 0; i < 6; i++ )
    {
        arrow -> set_expand();
        arrow -> update();
    }

    EXPECT_TRUE ( arrow -> check_collision(testMonster->get_pos_x(), testMonster->get_pos_y()) );
}

TEST(ExitTest, )
{
    system("Pause");
}



